#include "Buffer.h"
#include "IBuffer.h"

IBuffer::IBuffer() {

	isInitialized = false;
}

IBuffer::~IBuffer() {

	if (isInitialized) {

		if(pthread_detach(thread) != 0) {
			puts("detach of thread failed");
		}
		if(pthread_cancel(thread) != 0) {
			puts("cancel of thread failed");
		}

		//close file
		close(file);

		// free memory of all three buffers
		free(buffer0);
		free(buffer1);
		free(buffer2);

		// free memory of semaphores
		sem_destroy(empty);
		sem_destroy(full);

		free(empty);
		free(full);
	}

}

// Initialize internal buffer
bool IBuffer::init(const char* inputfile) {

	isInitialized = true;

	//not yet finished
	isFinished = false;

	//enable all three buffers to get filled from input file
	buffer0isEmpty = true;
	buffer1isEmpty = true;
	buffer2isEmpty = true;

	empty = new sem_t;
	full = new sem_t;

	//initialize semaphores
	if (sem_init(empty, 0, 3) == -1) {
		fputs("Failed to initialize semaphore EMPTY \n", stderr);
		return false;
	}
	if (sem_init(full, 0, 0) == -1) {
		fputs("Failed to initialize semaphore FULL \n", stderr);
		return false;
	}

	// first run
	isFirstRun = true;

	//set first buffer (buffer 0) as active
	currentBuffer = 0;

	//set file index to beginning of file
	fileIndex = 0;

	//set index to the beginning of buffer
	bufferIndex = 0;

	//align memory for the three buffers
	buffer0 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer0 == NULL) {
		fputs("Buffer 0 Memalign failed\n", stderr);
		return false;
	}
	buffer1 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer1 == NULL) {
		fputs("Buffer 1 Memalign failed\n", stderr);
		return false;
	}
	buffer2 = (char*) memalign(BUFFER_SIZE, BUFFER_SIZE);
	if (buffer2 == NULL) {
		fputs("Buffer 2 Memalign failed\n", stderr);
		return false;
	}

	//open input file
	file = open(inputfile, ODIRECT | O_RDONLY);
	if (file == -1) {
		fputs("couldn't open input file\n", stderr);
		return false;
	}

	//start thread
	if (startThread(&thread, this) == EXIT_FAILURE) {
		fputs("couldn't start thread\n", stderr);
		return false;
	}

	return true;

}


// Go one step back in internal buffer
//return values:
//		 0 = success
//		-1 = string too long
//		-2 = Beginning of file reached
int IBuffer::ungetChar(size_t numberOfChars) {

	// if number of chars is longer than 2 internal buffers -> doesn't work -> too long
	if (numberOfChars > (2 * BUFFER_SIZE)) {
		return -1;
	}
	for (size_t i = 0; i < numberOfChars; i++) {

		// make sure file index is greater than 0 !!!
		if (fileIndex <= 0) {
			return -2;
		}

		if (bufferIndex == 0) {
			bufferIndex = (BUFFER_SIZE - 1);
			currentBuffer = (currentBuffer - 1) % 3;
		} else {
			bufferIndex--;
		}
		fileIndex--;
		isFinished = false;

	}
	return 0;
}

// return the next char from internal buffer
char IBuffer::getChar() {

	char c;
	// if not already finished
	if (!isFinished) {

		// Beginning of buffer ==> new buffer ==> check semaphore
		if (bufferIndex == 0) {
			//decrement semaphore FULL
			sem_wait(full);

			// get next char of current buffer
			c = getCharFromCurrentBuffer();

		} else if (bufferIndex > 0) {
			// Middle of buffer ==> nothing to do with semaphore
			// get next char of current buffer
			c = getCharFromCurrentBuffer();

			// end of buffer
			if (bufferIndex == BUFFER_SIZE) {
				currentBuffer = (currentBuffer + 1) % 3;
				bufferIndex = 0;
				if (currentBuffer == 0) {
					//enable buffer 1 to get rewritten
					buffer1isEmpty = true;
					sem_post(empty);
				} else if (currentBuffer == 1) {
					// only if NOT first cycle!!!
					if (!isFirstRun) {
						//enable buffer 2 to get rewritten
						buffer2isEmpty = true;
						sem_post(empty);
					}
					//first run is over
					isFirstRun = false;
				} else if (currentBuffer == 2) {
					//enable buffer 0 to get rewritten
					buffer0isEmpty = true;
					sem_post(empty);
				} else {
					assert(false);
				}
			}
		}

		if (c == '\0') {
			isFinished = true;
		}
		return c;

	} else {
		// Finished -> end of file
		return '\0';
	}
}

// get next char of current buffer
char IBuffer::getCharFromCurrentBuffer() {

	char c = '\0';
	if (currentBuffer == 0) {
		c = buffer0[bufferIndex];
	} else if (currentBuffer == 1) {
		c = buffer1[bufferIndex];
	} else if (currentBuffer == 2) {
		c = buffer2[bufferIndex];
	}else {
		assert(false);
	}
	//next char in buffer
	bufferIndex++;
	//one more char in file
	fileIndex++;

	return c;
}

//return values:
//		 0 = success
//		-1 = string too long
//		-2 = Beginning of file reached
int IBuffer::copyString(size_t lastChars, char* position) {

	int tmp = ungetChar(lastChars);

	if (tmp == 0) {

		// write length of string in first position in front of string
		position[0] = lastChars;
		for (size_t i = 1; i <= lastChars; ++i) {
				position[i] = getChar();
		}
		position[lastChars + 1] = '\0';  // for easier output
	}
	return tmp;
}

bool IBuffer::run() {

	// decrement the semaphore EMPTY
	sem_wait(empty);
	if (buffer0isEmpty) {
		buffer0isEmpty = false;
		if (read(file, &buffer0[0], BUFFER_SIZE) == -1) {
			fputs("couldn't read from file into buffer 0\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else if (buffer1isEmpty) {
		buffer1isEmpty = false;
		if (read(file, &buffer1[0], BUFFER_SIZE) == -1) {
			fputs("couldn't read from file into buffer 1\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else if (buffer2isEmpty) {
		buffer2isEmpty = false;
		if (read(file, &buffer2[0], BUFFER_SIZE) == -1) {
			fputs("couldn't read from file into buffer 2\n", stderr);
			//Error -> terminate thread
			pthread_exit(NULL);
		}
	} else {

		assert(false);
		//Error -> terminate thread
		pthread_exit(NULL);
	}
	// increment semaphore FULL
	sem_post(full);

	return !isFinished;
}
